﻿// ================================================================================
// The contents of this file are subject to the Mozilla Public License
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/2.0/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
// ================================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows;
using Reflex.Framework;
using Reflex.Framework.Communication;
using Reflex.Framework.Mathematics;
using Reflex.Framework.Remoting;
using Reflex.Framework.View;
using Reflex.Interface;
using Reflex.Interface.Aion;
using Reflex.Os.Aion.View;

namespace Reflex.Os.Aion.Service
{
	/// <summary>
	/// Represents the IntelService.
	/// </summary>
	[Export( typeof( IReflex ))]
	public class RadarService : ViewModelCommunication, IReflex
	{
		/// <summary>
		/// Indicates whether the radar requires a refresh.
		/// </summary>
		bool _bRefresh;

		/// <summary>
		/// Contains the application instance.
		/// </summary>
		private IAionInstance _hAionInstance;

		/// <summary>
		/// Contains the collection observer.
		/// </summary>
		private ReObjectObserverCollection<IActor> _hCollection;

		/// <summary>
		/// Contains the configuration window.
		/// </summary>
		private WindowView _hConfiguration;

		/// <summary>
		/// Contains each actor observer.
		/// </summary>
		private Dictionary<uint, ReObjectObserver> _hObserver;

		/// <summary>
		/// Contains each actor identifier to be refreshed.
		/// </summary>
		private HashSet<uint> _hRefresh;

		/// <summary>
		/// Contains each render object.
		/// </summary>
		public HashSet<RadarRenderService> _hRender;

		/// <summary>
		/// Contains the setting.
		/// </summary>
		private Setting _hSetting;

		/// <summary>
		/// Contains the radar view.
		/// </summary>
		private RadarView _hWindow;
		
		/// <summary>
		/// Refresh the render image(s) and, optionally, save all settings.
		/// </summary>
		/// <param name="bRequireSettingSave">Indicates whether a settings save is required.</param>
		private void _RefreshRender( bool bRequireSettingSave )
		{
			// Lock this object.
			lock( this )
			{
				// Iterate through each render object.
				foreach( RadarRenderService hIntelRadarService in _hRender )
				{
					// Update the bitmap.
					hIntelRadarService.Update();
				}
			}

			// Save the setting.
			if ( bRequireSettingSave ) _hSetting.Save();
		}

		/// <summary>
		/// Contains the image.
		/// </summary>
		public byte[] Image
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<byte[]>( "Image" );
			}
			set
			{
				// Set the property.
				PropertySet( "Image", value );
			}
		}

		#region Configuration
		/// <summary>
		/// Indicates whether creatures are shown.
		/// </summary>
		public bool Creature
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Creature, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Creature, value ))
				{
					// Set the setting.
					_hSetting["Creatures"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}

		/// <summary>
		/// Indicates whether enemy players are shown.
		/// </summary>
		public bool Enemy
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Enemy, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Enemy, value ))
				{
					// Set the setting.
					_hSetting["Enemy"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}

		/// <summary>
		/// Indicates whether friendly players are shown.
		/// </summary>
		public bool Friendly
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Friendly, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Friendly, value ))
				{
					// Set the setting.
					_hSetting["Friendly"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}

		/// <summary>
		/// Indicates whether gatherables are shown.
		/// </summary>
		public bool Gatherable
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Gatherable, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Gatherable, value ))
				{
					// Set the setting.
					_hSetting["Gatherable"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}

		/// <summary>
		/// Indicates whether group players are shown.
		/// </summary>
		public bool Group
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Group, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Group, value ))
				{
					// Set the setting.
					_hSetting["Group"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}

		/// <summary>
		/// Indicates whether kisks are shown.
		/// </summary>
		public bool Kisk
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Kisk, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Kisk, value ))
				{
					// Set the setting.
					_hSetting["Kisk"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}

		/// <summary>
		/// Indicates whether objects are shown.
		/// </summary>
		public bool Object
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => Object, true );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => Object, value ))
				{
					// Set the setting.
					_hSetting["Object"].Value = value.ToString();

					// Refresh the render image(s) and save all settings.
					_RefreshRender( true );
				}
			}
		}
		#endregion

		#region IDisposable
		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			// Lock this object.
			lock( this )
			{
				// Check if the collection observer is available.
				if ( _hCollection != null )
				{
					// Dispose of the collection observer.
					_hCollection.Dispose();

					// Remove the collection observer.
					_hCollection = null;
				}

				// Iterate through each individual observer.
				foreach( ReObjectObserver hReObjectObserver in _hObserver.Values )
				{
					// Dispose of the observer.
					hReObjectObserver.Dispose();
				}

				// Clear the dictionary.
				_hObserver.Clear();

				// Clear the set.
				_hRefresh.Clear();

				// Clear the dictionary.
				_hRender.Clear();

				// Check if the window is available.
				if ( _hWindow != null )
				{
					// Set the position on the X-axis.
					_hSetting["Left"].Value = _hWindow.Left.ToString();

					// Set the position on the Y-axis.
					_hSetting["Top"].Value = _hWindow.Top.ToString();

					// Save the setting.
					_hSetting.Save();
				}
			}
		}
		#endregion

		#region IService
		/// <summary>
		/// Occurs when the button has been invoked.
		/// </summary>
		public Action Click
		{
			get
			{
				// Return the delegate.
				return () =>
				{
					// Invoke the delegate on the UI thread.
					Application.Current.Dispatcher.BeginInvoke( new Action(() =>
					{
						// Check if a window is to be created.
						if ( _hConfiguration == null || !_hConfiguration.IsVisible )
						{
							// Initialize a new instance of the RadarConfigurationView class.
							RadarConfigurationView hRadarConfigurationView = new RadarConfigurationView();

							// Initialize a new instance of the ObservableCollection class.
							hRadarConfigurationView.DataContext = this;

							// Set the window title.
							hRadarConfigurationView.Title = string.Format( "Radar Configuration ({0})", _hAionInstance.Name );

							// Initialize a new instance of the WindowView class.
							_hConfiguration = new WindowView();

							// Set the data context.
							_hConfiguration.DataContext = hRadarConfigurationView;

							// Show the window.
							_hConfiguration.Show();
						}
					}));
				};
			}
		}

		/// <summary>
		/// Contains the description.
		/// </summary>
		public string Description
		{
			get
			{
				// Return the description.
				return "Enables an out-of-game radar.";
			}
		}

		/// <summary>
		/// Initialize the service.
		/// </summary>
		/// <param name="Instance">Contains the instance for which to provide a service.</param>
		/// <param name="Setting">Contains the setting.</param>
		public bool Initialize( IReflexServerInstance Instance, Setting Setting )
		{
			// Check if the value is an Aion application instance.
			if ( Instance is IAionInstance )
			{
				// Set the application instance.
				_hAionInstance = ( Instance as IAionInstance );
				
				// Occurs after a frame has been completed.
				_hAionInstance.Frame += () =>
				{
					// Check if a refresh is required.
					if ( IsEnabled && ( _bRefresh || _hRefresh.Count != 0 ))
					{
						// Retrieve the controller actor.
						IControllerActor hControllerActor = _hAionInstance.Controller.Actor;

						// Check if the controller actor is available.
						if ( hControllerActor != null )
						{
							// Retrieve the player image.
							using( Bitmap hBitmapPlayer = global::Reflex.Os.Aion.Properties.Resource.RadarPlayer )
							{
								// Retrieve the radar image.
								using( Bitmap hBitmap = global::Reflex.Os.Aion.Properties.Resource.Radar )
								{
									// Retrieve the radar graphics.
									using( Graphics hGraphics = Graphics.FromImage( hBitmap ))
									{
										// Retrieve the center position.
										int iCenterPosition = hBitmap.Height / 2;

										// Lock this object.
										lock( this )
										{
											// Iterate through each render object.
											foreach( RadarRenderService hIntelRadarService in _hRender.Where( x => x.Bitmap != null && x.Actor.Id != hControllerActor.Id ).OrderBy( x => x.Player ))
											{
												// Check if the object position is to be re-calculated.
												if ( _bRefresh || _hRefresh.Contains( hIntelRadarService.Actor.Id ))
												{
													// Retrieve the relative position.
													Vector2d hVector2d = _hAionInstance.Controller.MapPosition2d( hIntelRadarService.Actor.Position );

													// Check if the relative position is available.
													if ( hVector2d != null )
													{
														// Update the position on the X-axis and scale 100 meter to 85 pixels (25 meter/circle).
														hIntelRadarService.X = iCenterPosition - ( hVector2d.X / 100 * 85 );

														// Update the position on the Y-axis and scale 100 meter to 85 pixels (25 meter/circle).
														hIntelRadarService.Y = iCenterPosition - ( hVector2d.Y / 100 * 85 );
													}
													// Otherwise reset the position.
													else hIntelRadarService.X = hIntelRadarService.Y = 0;
												}

												// Check if the calculated position is within the image boundaries.
												if ( hIntelRadarService.X > 0 && hIntelRadarService.X <= hBitmap.Width && hIntelRadarService.Y > 0 && hIntelRadarService.Y <= hBitmap.Height )
												{
													// Draw the image.	
													hGraphics.DrawImageUnscaled( hIntelRadarService.Bitmap, ( int ) hIntelRadarService.X, ( int ) hIntelRadarService.Y );
												}
											}
										}

										// Initialize an image to rotate the player image.
										using( Bitmap hBitmapRotate = new Bitmap( hBitmapPlayer.Width, hBitmapPlayer.Height ))
										{
											// Retrieve the graphics object to paint the rotated image.
											using( Graphics hGraphicsRotate = Graphics.FromImage( hBitmapRotate ))
											{
												// Change the coordinate system to allow rotation.
												hGraphicsRotate.TranslateTransform( hBitmapPlayer.Width / 2, hBitmapPlayer.Height / 2 );
												
												// Rotate the graphics according to the difference between the camera and player rotation.
												hGraphicsRotate.RotateTransform( _hAionInstance.Controller.CameraYaw - hControllerActor.Rotation );
												
												// Restore the original coordinate system.
												hGraphicsRotate.TranslateTransform( -hBitmapPlayer.Width / 2, -hBitmapPlayer.Height / 2 );
												
												// Rotate the image.
												hGraphicsRotate.DrawImageUnscaled( hBitmapPlayer, 0, 0 );

												// Draw the rotated image on the radar.
												hGraphics.DrawImageUnscaled( hBitmapRotate, iCenterPosition - ( hBitmapPlayer.Width / 2 ), iCenterPosition - ( hBitmapPlayer.Height / 2 ));
											}
										}

										// Initialize a new instance of the MemoryStream class.
										using( MemoryStream hMemoryStream = new MemoryStream())
										{
											// Save the modified image to the stream.
											hBitmap.Save( hMemoryStream, hBitmap.RawFormat );

											// Set the image.
											Image = hMemoryStream.ToArray();
										}

										// Set the status indicating whether the radar requires a refresh.
										_bRefresh = false;

										// Clear the refresh actor identifier list.
										_hRefresh.Clear();									
									}
								}
							}
						}
					}
				};

				// Initialize a new instance of the Dictionary class.
				_hObserver = new Dictionary<uint, ReObjectObserver>();

				// Initialize a new instance of the HashSet class.
				_hRefresh = new HashSet<uint>();

				// Initialize a new instance of the HashSet class.
				_hRender = new HashSet<RadarRenderService>();

				// Seperate the circumstance observers from the initialization code.
				if ( 1 == 1 )
				{
					// Initialize a new instance of the ReObjectObserver class.
					ReObjectObserver hControllerObserver = new ReObjectObserver( _hAionInstance.Controller );

					// Initialize a new instance of the ReObjectObserverCollection class.
					ReObjectObserverCollection<IGroupMember> hGroupObserver = new ReObjectObserverCollection<IGroupMember>( _hAionInstance.Group );

					// Add a changed handler to the CameraYaw property.
					hControllerObserver.Add( "CameraYaw", () =>
					{
						// Set the status indicating the radar requires a refresh.
						_bRefresh = true;
					});

					// Add a changed handler to the Search property.
					hControllerObserver.Add( "Search", () =>
					{
						// Refresh the render image(s).
						_RefreshRender( false );
					});

					// Add an observer to the group to refresh images.
					hGroupObserver.Added(( hGroupMember ) =>
					{
						// Refresh the render image(s).
						_RefreshRender( false );
					})();

					// Add an observer to the group to refresh images.
					hGroupObserver.Added(( hGroupMember ) =>
					{
						// Refresh the render image(s).
						_RefreshRender( false );
					});
				}

				// Attempt the following code.
				try
				{
					// Initialize a boolean.
					bool bValue;

					// Set the setting.
					_hSetting = Setting;
					
					// Set the Creature value.
					if ( bool.TryParse( _hSetting["Creature"].Value, out bValue )) PropertySet(() => Creature, bValue );
					
					// Set the Enemy value.
					if ( bool.TryParse( _hSetting["Enemy"].Value, out bValue )) PropertySet(() => Enemy, bValue );
					
					// Set the Friendly value.
					if ( bool.TryParse( _hSetting["Friendly"].Value, out bValue )) PropertySet(() => Friendly, bValue );
					
					// Set the Gatherable value.
					if ( bool.TryParse( _hSetting["Gatherable"].Value, out bValue )) PropertySet(() => Gatherable, bValue );
					
					// Set the Group value.
					if ( bool.TryParse( _hSetting["Group"].Value, out bValue )) PropertySet(() => Group, bValue );

					// Set the Kisk value.
					if ( bool.TryParse( _hSetting["Kisk"].Value, out bValue )) PropertySet(() => Kisk, bValue );

					// Set the Object value.
					if ( bool.TryParse( _hSetting["Object"].Value, out bValue )) PropertySet(() => Object, bValue );

					// Return true.
					return true;
				}
				// Catch an exception.
				catch
				{
					// Return true.
					return true;
				}
			}
			// Otherwise return false.
			else return false;
		}

		/// <summary>
		/// Indicates whether the service is enabled.
		/// </summary>
		public bool IsEnabled
		{
			get
			{
				// Retrieve the property.
				return PropertyGet<bool>(() => IsEnabled );
			}
			set
			{
				// Set the property.
				if ( PropertySet(() => IsEnabled, value ))
				{
					// Check if the property is enabled.
					if ( !value )
					{
						// Invoke the delegate on the UI thread.
						Application.Current.Dispatcher.BeginInvoke( new Action(() =>
						{
							// Hide the window.
							if ( _hWindow != null ) _hWindow.Hide();
						}));

						// Dispose of the service.
						Dispose();
					}
					// Otherwise the property is not enabled.
					else
					{
						// Invoke the delegate on the UI thread.
						Application.Current.Dispatcher.BeginInvoke( new Action(() =>
						{
							// Check if the window has not been instantiated.
							if ( _hWindow == null )
							{
								// Initialize a new instance of the RadarView class.
								_hWindow = new RadarView();

								// Set the datacontext.
								_hWindow.DataContext = this;

								// Retrieve the position on the X-axis.
								if ( !string.IsNullOrEmpty( _hSetting["Left"].Value )) _hWindow.Left = double.Parse( _hSetting["Left"].Value );

								// Retrieve the position on the Y-axis.
								if ( !string.IsNullOrEmpty( _hSetting["Top"].Value )) _hWindow.Top = double.Parse( _hSetting["Top"].Value );
							}
							
							// Set the window title.
							_hWindow.Title = _hAionInstance.Name;

							// Show the window.
							_hWindow.Show();
						}));

						// Initialize a new instance of the ReObjectObserverCollection class.
						_hCollection = new ReObjectObserverCollection<IActor>( _hAionInstance.Actor );

						// Add an added event handler to the collection.
						_hCollection.Added(( Actor ) =>
						{
							// Lock this object.
							lock( this )
							{
								// Initialize a new instance of the ReObjectObserver class.
								ReObjectObserver hReObjectObserver = new ReObjectObserver( Actor );

								// Add the observer to the observer collection.
								_hObserver.Add( Actor.Id, hReObjectObserver );

								// Add the actor to the render collection.
								_hRender.Add( new RadarRenderService( _hAionInstance, Actor, this ));							

								// Add a changed handler to the position property.
								hReObjectObserver.Add( "Position", () =>
								{
									// Check if a refresh has not been required.
									if ( !_bRefresh )
									{
										// Retrieve the controller actor.
										IControllerActor hControllerActor = _hAionInstance.Controller.Actor;

										// Check if the position of the controller actor changed.
										if ( hControllerActor != null && hControllerActor.Id == Actor.Id )
										{
											// Set the status indicating the radar requires a refresh.
											_bRefresh = true;
										}
										// Otherwise add the actor identifier to be refreshed.
										else _hRefresh.Add( Actor.Id );
									}
								})();

								// Check if this is a npc actor.
								if ( Actor is IActorNpc )
								{
									// Add a changed handler to the stance property.
									hReObjectObserver.Add( "Stance", () =>
									{
										// Check if the npc has died.
										if (( Actor as IActorNpc  ).Stance == ActorCreatureStance.Dead )
										{
											// Retrieve the service object.
											RadarRenderService hIntelRadarService = _hRender.SingleOrDefault( x => x.Actor == Actor );

											// Remove the actor from the radar service.
											if ( hIntelRadarService != null ) _hRender.Remove( hIntelRadarService );
										}
									});
								}
							}
						})();

						// Add a removed event handler to the collection.
						_hCollection.Removed(( Actor ) =>
						{
							// Lock this object.
							lock( this )
							{
								// Check if the actor does exist in the collection (disposed?).
								if ( _hObserver.ContainsKey( Actor.Id ))
								{
									// Retrieve the service object.
									RadarRenderService hIntelRadarService = _hRender.SingleOrDefault( x => x.Actor == Actor );

									// Dispose of the observer.
									_hObserver[Actor.Id].Dispose();

									// Remove the observer from the observer collection.
									_hObserver.Remove( Actor.Id );

									// Remove the actor from the radar service.
									if ( hIntelRadarService != null ) _hRender.Remove( hIntelRadarService );
								}
							}
						});
					}
				}
			}
		}		

		/// <summary>
		/// Indicates whether the service is a role.
		/// </summary>
		public bool IsRole
		{
			get
			{
				// Return false.
				return false;
			}
		}

		/// <summary>
		/// Contains the name.
		/// </summary>
		public string Name
		{
			get
			{
				// Return the name.
				return "Radar";
			}
		}
		#endregion
	}
}